Kattya Cuevas

Follow this blog

Evitar Re-Renders al usar Context en React

Hace algunos meses, estaba haciendo un refactor en un proyecto de React y me quedé horas intentando resolver el problema. El refactor se debía a un problema típico de proyectos de React, pasar muchos props a los componentes hijos para así pasarlos a los hijos de estos y así. Cuando este tipo de situaciones pasa, al querer actualizar alguno de los componentes o quizá solo quieras reutilizarlos en alguna otra parte, te obliga a tener información en tu nuevo componente que no necesitas en ese momento. English version here En fin, al terminar el refactor, separé la información en varios contextos, para así solo compartir la data necesaria con los componentes que lo necesitaban. Aunque suena como un refactor exitoso, no lo era, mis componentes se seguían actualizando cuando actualizaba un estado de un contexto del que no dependían. No tiene sentido, ¿verdad? Para explicar mi problema, pondré un ejemplo. Tengo 3 componentes: SessionForm: Componente para agregar tu username. Si ya lo has ingresado, entonces te muestra un saludo y un botón para desloguearte (borrar el username). Si no lo has ingresado, te muestra un input para agregarlo. SessionCounterMessage: Componente que muestra un mensaje con el username ingresado o un ‘You’ y el número que devuelva mi contador. CounterButtons: Componente que tiene un contador. Son 2 botones que puedes sumar o restar al counter. Siguiendo mi primera solución, aquí crearía 2 contextos. Uno para el username (SessionContext) y otro para el counter (CounterContext). Entonces la dependencia de contextos de mis componentes quedaría así: SessionForm depende de SessionContext CounterButtons depende de CounterContext SessionCounterMessage depende de SessionContext y CounterContext Esta fue mi solución inicial: function App() { const [currentUser, setCurrentUser] = React.useState(null); const [counter, setCounter] = React.useState(1); return ( <SessionContext.Provider value={React.useMemo(() => ({ currentUser, setCurrentUser }), [ currentUser, setCurrentUser ])} > <CounterContext.Provider value={React.useMemo(() => ({ counter, setCounter }), [ counter, setCounter ])} > <SessionForm /> <SessionCounterMessage /> <CounterButtons /> </CounterContext.Provider> </SessionContext.Provider> ); } Para que se den cuenta de mi error, agregué un console.log a mis componentes para que vean cuántas veces se renderizaba: Allí pueden ver que cuando actualizo el counter, se vuelve a renderizar el componente SessionForm, a pesar de que no depende del contexto CounterContext que es quien tiene a counter como estado. Y que cuando actualizo el username se vuelve a renderizar el componente CounterButtons, que no depende del contexto SessionContext, que tiene a username como estado. Ahora que vieron mi código, ¿encontraron el error? Bueno, yo no encontraba fallas en mi lógica. Si los había separado en diferentes contextos. Entonces ¿por qué se seguían renderizando todos los componentes? Lo que hice fue pedir ayuda. Le pregunté a @sergiodxa que tiene más tiempo usando React y me dijo: Esto const MyContext = React.useContext({}); function App() { const [state, setState] = React.useState(false); return ( <MyContext.Provider value={ { state, setState } }> <MyCustomComponent /> </MyContext.Provider> ); } Es diferente a esto: const MyContext = React.useContext({}); function MyContextProvider({ children }) { const [state, setState] = React.useState(false); return ( <MyContext.Provider value={ { state, setState } }> {children} </MyContext.Provider> ); } function App() { return ( <MyContextProvider> <MyCustomComponent /> </MyContextProvider> ); } No me explicó el por qué en ese momento, quizá estaba ocupado, no lo recuerdo. Pero me di cuenta que estaba renderizando mi componente en el mismo lugar que estaba creando mis estados. Así que cada vez que actualizaba el estado, volvió a renderizar mi componente padre, que a su vez renderizaba a todos sus hijos. Con esto en mente, voy a cambiar el ejemplo que les dí al inicio, para comprobar que realmente funciona. function SessionProvider({ children }) { const [currentUser, setCurrentUser] = React.useState(null); return ( <SessionContext.Provider value={React.useMemo(() => ({ currentUser, setCurrentUser }), [ currentUser, setCurrentUser, ])} > {children} </SessionContext.Provider> ); } function CounterProvider({ children }) { const [counter, setCounter] = React.useState(1); return ( <CounterContext.Provider value={React.useMemo(() => ({ counter, setCounter }), [ counter, setCounter, ])} > {children} </CounterContext.Provider> ); } function App() { return ( <SessionProvider> <CounterProvider> <SessionForm /> <SessionCounterMessage /> <CounterButtons /> </CounterProvider> </SessionProvider> ); } Aquí pueden ver los logs de las veces que se renderiza cada componente ¡Funciona! ¡No más renders innecesarios! Puede parecer una cambio muy pequeño, incluso se puede llegar a pensar que el usuario no se va a dar cuenta. Pero los componentes que estaba refactorizando renderizaban audios y videos. Cada vez que hacían un cambio respecto a los audios, los videos se volvían a renderizar y se sentía como un bug en la aplicación. Si llegaron hasta aquí, gracias por leerme. ❤️

Fixing Re-Renders When Using Context in React

Some months ago, I was refactoring a React project, and I was stuck in one problem for hours. The refactor was because of a common problem in React projects: Pass a lot of props to the child components, then you have to pass them to the child of them, and so. When this happens, if you want to reuse those components on another part of the app, you have to get information in your new component that maybe you don’t need to worry about that time. Versión en español aquí I separated the data into many contexts, so I only share the necessary data with the component that needs them. So I stopped to pass a lot of props in every component. Even that sounds like a successful refactor, it wasn’t. My components keep updating when I updated an state of a context which they didn’t depend on. It doesn’t make sense, right? To explain my problem, I’ll give you an example. I’ll have 3 components: SessionForm: Component to add a username. If you have already entered it, it shows a greeting and a button to log out (delete the username). If you haven’t entered it, it shows you an entry to add it. SessionCounterMessage: Component that shows a message with the username entered or a You and the number returned by a counter. CounterButtons: Component with a counter and 2 buttons that allow you to add or subtract from the counter. Based on my first solution, I would create 2 contexts. One for the username (SessionContext) and one for the counter ( CounterContext). Then the dependency of contexts of my components would look like this: SessionForm depends on SessionContext CounterButtons depends on CounterContext SessionCounterMessage depends on SessionContext and CounterContext This was my initial solution: function App() { const [currentUser, setCurrentUser] = React.useState(null); const [counter, setCounter] = React.useState(1); return ( <SessionContext.Provider value={React.useMemo(() => ({ currentUser, setCurrentUser }), [ currentUser, setCurrentUser, ])} > <CounterContext.Provider value={React.useMemo(() => ({ counter, setCounter }), [ counter, setCounter, ])} > <SessionForm /> <SessionCounterMessage /> <CounterButtons /> </CounterContext.Provider> </SessionContext.Provider> ); } I added a console.log to my components to make you aware of my error, I added a console.log to my components so that they see how many times it was rendered: There you can see, when I update the counter, it re-renders the SessionForm component. Even when it doesn’t depend on the CounterContext context, which has counter state. And when I update the username, it re-renders the CounterButtons component. Even when it doesn’t depend on the SessionContext context, which has username as a state. Now you see my code, do you find my mistake? Well, I didn’t find any mistakes in my code if I had separated them into different contexts. Why did they keep re-render all the components? What I did was ask for help. I asked @sergiodxa, who has been using React longer, and he said: This const MyContext = React.useContext({}); function App() { const [state, setState] = React.useState(false); return ( <MyContext.Provider value=> <MyCustomComponent /> </MyContext.Provider> ); } is different from this: const MyContext = React.useContext({}); function MyContextProvider({ children }) { const [state, setState] = React.useState(false); return ( <MyContext.Provider value=> {children} </MyContext.Provider> ); } function App() { return ( <MyContextProvider> <MyCustomComponent /> </MyContextProvider> ); } He didn’t explain why at that time; maybe he was busy, I don’t remember. But I realized that I was rendering my component in the same place that I created my states. Every time I updated the state, it re-rendered my parent component, which re-render all its children. With this in my mind, I’ll change my initial example to check it works. function SessionProvider({ children }) { const [currentUser, setCurrentUser] = React.useState(null); return ( <SessionContext.Provider value={React.useMemo(() => ({ currentUser, setCurrentUser }), [ currentUser, setCurrentUser, ])} > {children} </SessionContext.Provider> ); } function CounterProvider({ children }) { const [counter, setCounter] = React.useState(1); return ( <CounterContext.Provider value={React.useMemo(() => ({ counter, setCounter }), [ counter, setCounter, ])} > {children} </CounterContext.Provider> ); } function App() { return ( <SessionProvider> <CounterProvider> <SessionForm /> <SessionCounterMessage /> <CounterButtons /> </CounterProvider> </SessionProvider> ); } Here you can see the logs when every component is rendered It works! No more unnecessary renders! It could look like a small change, and even you could think the user won’t notice this change. But the components I was refactoring rendered audios and videos. Every time I updated the audios, the videos would be re-rendered, and it looks like a bug in the app. If you made it this far, thanks for reading. ❤️

Cómo me afecto la “experiencia universitaria”

En el último taller #IamRemarkable que di, conté sobre una experiencia mía en la universidad, bueno, un super resumen, así que reflexioné un poco del tema. Esta experiencia solo se la he contado a personas cercanas a mí, y aún así nunca les dije cómo me sentía y que aún a veces me da miedo sobresalir a causa de esto. Cuando estaba en la universidad, sacaba buenas notas, enseñaba a mis compañeros y era responsable, por lo que los profesores me tenían más confianza. Y no tenía miedo de hablarle a los profesores, y decirle cuando algo me parecía injusto o preguntar cuando tenía dudas. No recuerdo la primera vez que mis compañeros empezaron a hablar mal de mí o estar en mi contra, pero les voy a contar de las dos veces que me hicieron sentir mal, y sola. La primera fue cuando un profesor me encargó recolectar todos los trabajos, a pesar que no era la delegada del salón. El profesor, sentía más en confianza con que yo lo hiciera y yo creía que no tenía la culpa de ello. Una de las veces que salí del salón a hacer otras cosas entre clases, uno de mis compañeros reunió al resto para ponerse de acuerdo para no enviarme los trabajos y hacerme quedar mal frente al profesor. Así lo dijeron. Cerca de la mitad del salón, quizá por miedo, no aceptó la propuesta de este compañero, así que me enviaron los trabajos. Un amigo me contó sobre lo que querían hacer, como un chiste. Así que en su momento, no dije ni hice nada. Pero me quedé pensando que quizá sí fue mi culpa por aceptar estar a cargo. La segunda fue algo peor que solo hablar delante de mis compañeros. Enviaron un mail a todos los de mi salón y otros alumnos de la facultad, hablando mal de mí y de una amiga, diciendo que nos regalaban la nota porque éramos amigas de los profesores. El correo era algo largo, pero ese era el resumen, no merecíamos el puesto que ocupábamos. Mis compañeros no me quisieron decir del mail, faltaba poco para mi cumpleaños. Sin embargo el mail llegó a oídos de los profesores, y directivos de mi facultad, que hablaron con mi salón un día que salí temprano de clases. Un amigo me hizo llegar el correo, porque se le escapó detalles de la reunión y se lo pedí. Al leer el mail, no sabía qué hacer, sólo empecé a llorar, no pude evitarlo. Mi mamá lo notó y me preguntó qué me pasaba, le señalé la computadora para que lo leyera, no tenía fuerza para hablar. Mi mamá, que siempre me dijo que termine la universidad cuando le decía que quería dejarle para estudiar por mi cuenta, en ese momento me preguntó si quería dejar la universidad o hacer un cambio de universidad o de carrera. No sabía qué hacer o qué decir, mi amigo estaba un poco preocupado así que le tuve que decir que estaba bien, pero me daba cólera lo que decían de mí. Pero en aquel entonces, solo pensaba qué hice mal, quizá no debí sacar buenas notas o hablar con los profesores o llamar la atención. Olvidé comentar que muchas veces yo hablé con los profesores para que les dieran otra oportunidad, porque varios de mis compañeros estaban muy cerca de pasar y yo había visto su esfuerzo. Y uno (o más) de ellos creía todo esto de mí. Quizá por eso me dolió tanto, porque hice cosas para ayudarlos, y aún así algo había hecho mal. Los días siguientes por suerte teníamos exámenes y podía decir que estaba ocupada haciendo los proyectos o estudiando, y así no tenía que verlos. Pero también se acercaba mi cumpleaños y mis amigos me preguntaban qué haría. Yo no quería nada, ni siquiera quería tener que pensar en eso. Días después, más amigos de otros años se enteraron también de lo que había pasado, y publicaron en los grupos de la facultad en redes sociales, defendiéndome, diciendo que yo me había ganado con esfuerzo el puesto en el que estaba y que ellos sabían lo que valía. Pero el problema es que yo empecé a dejar de pensar así. Por eso es que aún a veces tengo esta duda ¿será que merezco el lugar en el que estoy? Siempre pensé que tuve mala suerte en tener esos compañeros y me tocó este tipo de situaciones, pero ahora que lo pensé mejor, muchos de mis amigos de otros años también se llevaban bien con los profesores, incluso mejor que yo, pero nunca les pasó lo que me pasó a mí. (Machismo tal vez?) Tuve mucha suerte de tener un grupo de apoyo en ese momento, para ayudarme a olvidar lo que había pasado o al menos a que no duela tanto. Ahora, gracias a los talleres que he recibido y estoy facilitando, entendí que sí merezco lo que tengo hoy, y en ese momento también lo merecía, porque era fruto de mi esfuerzo y dedicación. Me hubiera gustado tanto haber llevado estos talleres antes y, así, no hubiera sufrido tanto en ese momento.

Mi primera app con React Native

React Native es un framework para desarrollar aplicaciones nativas iOS y Android con React. Hay dos formas de empezar una aplicación con React Native, la primera es usando el CLI (línea de comandos) de react-native y la segunda es usando el CLI de Expo. Expo es una buena opción ya que tiene varios componentes adicionales construidos a partir de ReactNative para tener control de más APIs nativas y además te permite hacer build y deploy de tus aplicaciones para iOS, Android y web a la vez. Para este tutorial, haré una aplicación de posts usando expo. Si quieres seguir usando la primera opción, te recomiendo hacer el setup inicial siguiendo la documentación oficial de ReactNative. Requerimientos: node yarn expo-cli $ npm install -g expo-cli Para este post tengo la versión 3.21.9 (Opcional) Emuladores Android y iOS, es opcional ya que con expo puedes probar desde tu celular, debes instalarte expo en tu celular. Para crear una aplicación, para este caso llamado MyPostApp, solo debes correr: $ expo init MyPostApp Escoge empty template, para empezar con una página en blanco. Para ingresar al proyecto: $ cd MyPostApp Para iniciar el proyecto: $ yarn start Expo te dará un código QR con el que podrás abrir la aplicación desde tu celular. Para empezar a editar tu aplicación, solo debes ir al archivo App.js. import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default function App() { return ( <View style={styles.container}> <Text>Open up App.js to start working on your app!</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, }); Llamamos a 3 componentes de ReactNative: View: Es un componente que representa un contenedor básico en la UI, este puede agrupar texto, imágenes, vídeos. Comparándolo con desarrollo web, es lo más parecido a una etiqueta div. Text: Este componente se usa solo para mostrar texto o anidar etiquetas de texto. Solo con esta etiqueta se puede mostrar texto. StyleSheet: Es una forma de escribir código CSS lo más parecido a CSS StyleSheets. Listar Posts Guardemos nuestros posts en un array por el momento, ya que no tendremos una interacción con una base de datos. const POSTS = [ { id: 1, body: "In irure minim in pariatur nisi irure reprehenderit cupidatat. Consequat ea enim veniam Lorem id nulla proident aute.", createdAt: new Date(2020, 3, 15), author: "elizabeth", }, { id: 2, body: "Nisi laborum ea ad sit exercitation eu incididunt elit nostrud excepteur irure enim. Magna do aliqua officia officia dolore ad proident. Occaecat cillum sit veniam ea nostrud deserunt duis cupidatat laboris enim nostrud deserunt ex non.", createdAt: new Date(2020, 5, 2), author: "elizabeth", }, { id: 3, body: "Nulla Lorem Lorem occaecat laboris minim anim sit ea eiusmod. Sunt ea ex exercitation est veniam.", createdAt: new Date(2020, 5, 25), author: "emma", }, ]; Para renderizar listas con ReactNative, se usa FlatList estos props son las más básicos: data: aquí se pasa nuestra estructura de datos renderItem: se pasa el componente a renderizar, este va a recibir item como prop, conteniendo cada uno de los ementos de la lista keyExtractor: Como es una lista, cada elemento debe tener un key export default function App() { return ( <View style={styles.container}> <FlatList data={POSTS} renderItem={({ item }) => <Text>{item.body}</Text>} keyExtractor={(post) => Number(post.id)} /> </View> ); } Si estas en iOS, verás que tu aplicación se renderiza sin importarle el StatusBar, para evitar que tome este espacio se usa SafeAreaView. Agreguemos un poco de estilos a nuestra lista de posts: Primero crearemos un componente para el card de cada post: // components/PostCard.js import React from "react"; import { View, Text, StyleSheet } from "react-native"; export default function PostCard({ post }) { return ( <View style={styles.postContainer}> <Text style={styles.postBody}>{post.body}</Text> <Text style={styles.postAuthor}>{post.author}</Text> </View> ); } const styles = StyleSheet.create({ postContainer: { backgroundColor: "#dfefff", marginHorizontal: 20, marginVertical: 8, borderWidth: 1, borderColor: "#d1dcdf", borderRadius: 5, padding: 10, }, postBody: { fontSize: 16, color: "#292944", }, postAuthor: { alignSelf: "flex-end", color: "#6e6e7e", fontSize: 14, } }); Actualizamos la página principal agregando un título y algunos estilos: export default function App() { return ( <SafeAreaView style={styles.container}> <Text style={styles.heading}>Posts List</Text> <FlatList data={POSTS} renderItem={({ item }) => <PostCard post={item} />} keyExtractor={post => post.body} /> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#b4cefe", alignItems: "center", justifyContent: "center", }, heading: { color: "#292944", fontSize: 24, fontWeight: "600", marginTop: 20, marginBottom: 10, } }); Agregar un post Para crear un post, se abrirá un modal con un formulario. Para esto, usaremos tres componentes nuevos: TouchableOpacity: Es un componente que al presionarlo cambiará la opacidad. Este componente puede encerrar muchos componentes dentro. Tendrá un prop onPress que recibe una función que se ejecutará cuando se presione el componente. Modal: Componente que representa un modal. Este recibe el prop visible que se encargará de mostrar o no la vista que se encuentre dentro de este componente. TextInput: Componente que representa un input de tipo texto, adicional puedes pedir que al presionarlo abra un teclado en especial. Primero crearemos nuestro componente con el contenido del modal, el formulario para agregar un nuevo post. Este componente recibirá tres props, visible que indicará si el modal se ve o no, setVisible será una función que recibe el nuevo valor de visible, y setPosts para editar la lista de posts inicial. Para los valores del formulario, tendremos 2 estados, body y author. Para la UI, usaremos dos TextInput y un TouchableOpacity. En el prop onChangeText de los TextInput cambiaremos los valores de cada estado. Para el body, le agregaremos el prop multiline con valor true, así daremos la impresión de que el body contiene más que una linea de textos. El TouchableOpacity lo usaremos para agregar un nuevo post a nuestra lista de posts y limpiar el valor de cada estado usado en el formulario. // components/NewPostModal.js import React from "react"; import { Modal, View, TouchableOpacity, Text, TextInput, StyleSheet, } from "react-native"; export default function NewPostModal({ visible, setVisible, setPosts }) { const [body, setBody] = React.useState(""); const [author, setAuthor] = React.useState(""); const createPost = () => { setPosts((posts) => [ ...posts, { id: Date.now(), body, author, createdAt: Date.now() }, ]); setBody(""); setAuthor(""); setVisible(false); }; return ( <Modal visible={visible} transparent={true}> <View style={styles.modalContainer}> <View style={styles.modalContent}> <TouchableOpacity onPress={() => setVisible(false)}> <View style={styles.closeButton}> <Text>X</Text> </View> </TouchableOpacity> <Text style={styles.title}>Nuevo post</Text> <View style={styles.inputGroup}> <TextInput placeholder="Body" style={[styles.textInput, { height: 35 }]} value={body} onChangeText={(text) => setBody(text)} multiline={true} /> </View> <View style={styles.inputGroup}> <TextInput placeholder="Author" style={styles.textInput} value={author} onChangeText={(text) => setAuthor(text)} /> </View> <TouchableOpacity onPress={() => createPost()} style={styles.createButton} > <Text>AGREGAR</Text> </TouchableOpacity> </View> </View> </Modal> ); } const styles = StyleSheet.create({ modalContainer: { backgroundColor: "rgba(0,0,0,0.8)", flex: 1, justifyContent: "center", alignItems: "center", paddingHorizontal: 25, }, modalContent: { backgroundColor: "#fff", width: "100%", paddingHorizontal: 15, paddingVertical: 20, }, closeButton: { alignSelf: "flex-end" }, title: { fontSize: 20, textAlign: "center", marginBottom: 12, }, inputGroup: { flexDirection: "row", marginVertical: 10 }, textInput: { flex: 1, borderBottomWidth: 1, borderColor: "#b7b7b7", height: 24, paddingVertical: 4, paddingHorizontal: 5, }, createButton: { alignSelf: "flex-end", borderWidth: 1, borderColor: "gray", paddingHorizontal: 20, paddingVertical: 5, marginTop: 15, }, }); Ahora agregaremos un botón en la página principal (App.js) que abrirá el nuevo modal que hemos creado. Para esto usaremos un TouchableOpacity. // App.js export default function App() { const [modalVisible, setModalVisible] = React.useState(false); const [posts, setPosts] = React.useState(POSTS); return ( <SafeAreaView style={styles.container}> <Text style={styles.heading}>Posts List</Text> <FlatList data={posts} renderItem={({ item }) => <PostCard post={item} />} keyExtractor={(post) => post.body} /> <TouchableOpacity onPress={() => setModalVisible(true)} style={styles.button}> <Text>Agregar post</Text> </TouchableOpacity> <NewPostModal visible={modalVisible} setVisible={setModalVisible} setPosts={setPosts} /> </SafeAreaView> ); } Con esto tenemos una app básica con ReactNative, con el que mostramos una lista de posts y podemos agregar un nuevo post en nuestra lista.

Building and Consuming a JSON API with Rails and React

When I thought about how to build an API, I started to search for what is the “best way” to do it. I found that exists specifications to build an API, you can found it here https://jsonapi.org. There, you would found a list of “rules” which we have to follow about how to send and receive data in an API. The next doubt I had, after knowing the “best way” to build an API, is how am I going to build that API with all those rules? It looks so much work to do. Well… That’s not true! In Rails, it’s easy with a gem called jsonapi-resources. In this project, the frontend will be done with React. The last version of Rails (v.6.0.0), Rails comes with Webpacker integrated (gem to handle the integration Rails + Webpack). It will make easier for us to use React. 🙌 Consume the data from our API with React, it’s not hard. But, formatting the data to send to the API could be complex. There is another library to do this! Also, this library is going to help you to validate the form data. This library is Formik. Let’s start! Versions of the tools we are going to use: Ruby 2.6.3 Rails 6.0.0 Yarn 1.17.3 Setup Base Project To create a new project with rails, we need to use the rails new command with the project name at the end. We could also add some additional options. In this case, we will use --database=postgresql to use PostgreSQL as our database, --skip-turbolinks to avoid using turbolinks because we will handle routing in the frontend, and --webpack=react to make Rails generate the configuration for us to use React.js. $ rails new my-app --database=postgresql --skip-turbolinks --webpack=react Now, we’re going to add a model called Post with 2 attributes: title and body. title is a string and body is a text. In Rails, the model represents the database tables. We can generate it with the rails generate model command followed by the model name with the attributes. The attributes should be separated by spaces and has the name and the type divided by :, like title:string. If we don’t specify the type of the attribute, Rails will default to the type string. The command generates a file with the model definition and a migration file that specifies the change to be made in the database, in this case, is the creation of the new table. $ rails generate model Post title body:text $ rails db:create $ rails db:migrate Note: We could also use rails g which is an alias of rails generate. The rails db:create command creates the database of the project and the rails db:migrate command runs all the pending migrations since this is a new project it will run every migration. We could add some seed data. To do it, we have to open the db/seeds.rb file and add the following lines: Post.create(title: "Post 1", body: "My first Post") Post.create(title: "Post 2", body: "My second Post") And to populate the database with our seed data, we need to run the command: $ rails db:seed In Rails projects, we should define the main route of the application this one is going to handle the path /. Go to config/routes.rb to define it and inside of the block Rails.application.routes.draw, add: root to: "home#index" get "*path", to: "home#index", constraints: { format: "html" } Note: The routes are defined as “home#index”, this means the controller which is going to control the behavior is HomeController and the specified action in the controller is index. We have to create the HomeController. First, let’s create the home_controller.rb file in app/controllers folder. Inside, add the index action: class HomeController < ApplicationController def index; end end Every action renders a view, in this case using HTML. We need to create the view in app/views/home folder and name it index.html.erb. In this file, we have to render the script to load our React app. <%= javascript_pack_tag 'posts' %> The helper javascript_pack_tag will generate the following script tag: <script src="/packs/js/posts-a447c92837fa3b701129.js"></script> Note: The name of the pack is generated with a hash added at the end to let us cached it for a long time. This script will load the pack posts.jsx. We have to create that pack in the app/javascript/packs folder: import React from "react"; import ReactDOM from "react-dom"; import App from "components/App"; document.addEventListener("DOMContentLoaded", () => { ReactDOM.render( <App />, document.body.appendChild(document.createElement("div")) ); }); We are going to use @reach/router to handle the routes in our React app. To install it, run: $ yarn add @reach/router Let’s create the component App.js in app/javascript/components folder. We will use this component to manage the routes. import React from "react"; import { Router } from "@reach/router"; import PostList from "./PostList"; function App() { return ( <Router> <PostList path="/" /> </Router> ); } export default App; Here we will create our first route /, which is going to render the PostList component. Now we are going to create the component PostList.js in app/javascript/components folder. import React from "react"; function PostList() { return <div>Hello from my React App inside my Rails App!</div>; } export default PostList; Inside we are going to render a div to test our React App. Start the Server We need to install foreman to run the React and Rails apps at the same time. We can install it with the command: $ gem install foreman We should create a Procfile.dev file in the root of the project. Inside it, add: web: bundle exec rails s webpacker: ./bin/webpack-dev-server To start the server, we need to run the command: $ foreman start -f Procfile.dev Create the API To create our API following the JSON:API specification, we are going to use the gem jsonapi-resources. To use it, we have to add it to the Gemfile and install it running bundle install. JSONAPI::Resources provides helper methods to generate correct routes. We’ll add the routes for API in config/routes.rb, before get "*path": namespace :api do jsonapi_resources :posts end Note: namespace :api is going to generate routes with /api before the route. Eg. /api/posts. We’re going to create the ApiController, to extend the controller from the ActionController::API module of Rails, and also we’re going to include the JSONAPI::ActsAsResourceController from JSONAPI::Resources. class ApiController < ActionController::API include JSONAPI::ActsAsResourceController end Now we need to create the PostsController. We should create it inside a folder named api because our routes config is going to search for an Api::PostsController class. class Api::PostsController < ApiController end jsonapi_resources :posts require a PostResource class defined. We have to create PostResource in app/resources/api/post_resource.rb. class Api::PostResource < JSONAPI::Resource attributes :title, :body end Here, we define the attributes and relationships we want to show as part of the resource. To see how our response looks like, go to localhost:5000/api/posts. Consume the API We will make the React app consume our API. First, let’s only read the data. Edit the PostList component to fetch the list of posts. import React, { useEffect, useState } from "react"; function PostList() { const [posts, setPosts] = useState([]); useEffect(() => { const requestPosts = async () => { const response = await fetch("/api/posts"); const { data } = await response.json(); setPosts(data); }; requestPosts(); }, []); return posts.map(post => <div>{post.attributes.title}</div>); } export default PostList; Inside a useEffect, we will do the fetch to /api/posts and save the response in the state of the component. Now, let’s create the form to add more posts. But first, we have to add formik as a dependency in the React app. $ yarn add formik We are going to create a new component to show the form, let’s call it AddPost.js. In this component, we are going to make a POST method to /api/posts with the correct format of data to create a new post. import React from "react"; import { navigate } from "@reach/router"; import { Formik, Field, Form } from "formik"; function AddPost() { const handleSubmit = values => { const requestPosts = async () => { // We get the CSRF token generated by Rails to send it // as a header in the request to create a new post. // This is needed because with this token, Rails is going to // recognize the request as a valid request const csrfToken = document.querySelector("meta[name=csrf-token]").content; const response = await fetch("/api/posts", { method: "POST", credentials: "include", headers: { "Content-Type": "application/vnd.api+json", "X-CSRF-Token": csrfToken }, body: JSON.stringify({ data: values }) }); if (response.status === 201) { navigate("/"); } }; requestPosts(); }; return ( <div> <h2>Add your post</h2> <Formik initialValues={\{ type: "posts", attributes: { title: "", body: "" } }} onSubmit={handleSubmit} render={() => ( <Form> <Field type="text" name="attributes.title" /> <Field type="text" name="attributes.body" /> <button type="submit">Create</button> </Form> )} /> </div> ); } export default AddPost; Finally, we need to add the route /add in our React app. import React from "react"; import { Router } from "@reach/router"; import PostList from "./PostList"; import AddPost from "./AddPost"; function App() { return ( <Router> <PostList path="/" /> <AddPost path="/add" /> </Router> ); } export default App; If we go to localhost:5000/add, we will see the form. If we fill the fields and click on Submit, it will create a new post and will navigate automatically to localhost:5000/, where we will see our new post as part of the list. If we reload the page, the React app will fetch our post again with the new post we just created. That’s how we can create an application with Rails + React, following the JSON:API spec. I would love any feedback about the post or the libraries used here. ❤️