I am currently reworking my DataProvider, updating it from a class component to a functional component with React Hooks.
I believe my issue is in the way I am setting up my context consumer but I haven't found a good way to test this.
DataProvider.js
import React, { createContext } from "react";
const DataContext = createContext();
export const DataProvider = (props) => {
const [test, setTest] = React.useState("Hello");
return (
<DataContext.Provider value={test}>{props.children}</DataContext.Provider>
);
};
export const withContext = (Component) => {
return function DataContextComponent(props) {
return (
<DataContext.Consumer>
{(globalState) => <Component {...globalState} {...props} />}
</DataContext.Consumer>
);
};
};
So my withContext function should receive a component and pass it the props of the Context Provider.
I try to pull in my test state into a component.
import React from "react";
import style from "./DesktopAboutUs.module.css";
import { withContext } from "../../DataProvider";
const DesktopAboutUs = ({ test }) => {
return (
<div className={style.app}>
<div>{test}</div>
</div>
);
};
export default withContext(DesktopAboutUs);
No data is showing up for test. To me this indicates that my withContext function is not properly receiving props from the Provider.
Because you passed value={test}, globalState is a string, not an object with a test property.
Either of these solutions will result in what you expected:
Pass an object to the value prop of DataContext.Provider using value={{ test }} instead of value={test} if you intend globalState to contain multiple props.
Pass globalState to the test prop of Component using test={globalState} instead of {...globalState} if you do not intend globalState to contain multiple props.
const DataContext = React.createContext();
const DataProvider = (props) => {
const [test, setTest] = React.useState("Hello");
return (
<DataContext.Provider value={{ test }}>
{props.children}
</DataContext.Provider>
);
};
const withContext = (Component) => (props) => (
<DataContext.Consumer>
{(globalState) => <Component {...globalState} {...props} />}
</DataContext.Consumer>
);
const DesktopAboutUs = ({ test }) => (
<div>{test}</div>
);
const DesktopAboutUsWithContext = withContext(DesktopAboutUs);
ReactDOM.render(
<DataProvider>
<DesktopAboutUsWithContext />
</DataProvider>,
document.querySelector('main')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
Related
Hello I am having trouble passing props between components. I can't share the exact code so I made a simplified version. I am not getting any console errors, though login is obviously 'undefined' Any insight is appreciated!
App.js
import React, { useState } from "react";
function App() {
const [login, setLogin] = useState('Jpm91297');
const changeState = () => {
const newLogin = document.getElementById('loginData').value;
setLogin(newLogin);
}
return (
<>
<h1>Fancy API Call!!!</h1>
<form onSubmit={() => changeState()}>
<input type='text' id='loginData'></input>
<button>Submit</button>
</form>
</>
);
}
export default App;
Api.Js
import React, {useEffect, useState} from "react";
const Api = ( { login } ) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`https://api.github.com/users/${login}`)
.then((res) => res.json())
.then(setData);
}, []);
if (data) {
return <div>{JSON.stringify(data)}</div>
}
return <div>No data Avail</div>
}
export default Api;
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Api from './api'
ReactDOM.render(
<>
<App />
<Api />
</>,
document.getElementById('root')
);
You are not preventing the default form action from occurring. This reloads the app.
You should lift the login state to the common parent of App and Api so it can be passed down as a prop. See Lifting State Up.
Example:
index.js
Move the login state to a parent component so that it can be passed down as props to the children components that care about it.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Api from './api';
const Root = () => {
const [login, setLogin] = useState('Jpm91297');
return (
<>
<App setLogin={setLogin} />
<Api login={login} />
</>
);
};
ReactDOM.render(
<Root />,
document.getElementById('root')
);
App
Pass the changeState callback directly as the form element's onSubmit handler and prevent the default action. Access the form field from the onSubmit event object.
function App({ setLogin }) {
const changeState = (event) => {
event.preventDefault();
const newLogin = event.target.loginData.value;
setLogin(newLogin);
}
return (
<>
<h1>Fancy API Call!!!</h1>
<form onSubmit={changeState}>
<input type='text' id='loginData'></input>
<button type="submit">Submit</button>
</form>
</>
);
}
Api
const Api = ({ login }) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`https://api.github.com/users/${login}`)
.then((res) => res.json())
.then(setData);
}, [login]); // <-- add login dependency so fetch is made when login changes
if (data) {
return <div>{JSON.stringify(data)}</div>;
}
return <div>No data Avail</div>;
};
I'm new to the webdev world and want to learn ReactJS. I followed a tutorial I found on YouTube made by Traversy where he makes a task tracker and now I want to make some changes to it to learn and practice some more.
I want to use context for the appointments (originally named tasks in the tutorial), add a calendar with react-calendar and use react-router-dom.
I got stuck for a while trying to make the list render, because it only rendered "empty". Later on found this post with a similar issue to mine: Only run a useEffect fetch after first useEffect fetch has fired and setUser in context
I changed bits of my code based on that post and now it does render the appointment list, but I don't know why it didn't work before and I'm unsure on why it does work now. I don't even know if I'm using context correctly or just prop-drilling. Help would be greatly appreciated. Thank you.
Also, sorry if my code is a mess, I'm new at this.
App.js
import { createContext, useState, useEffect } from "react";
import Dashboard from "./views/Dashboard";
import './App.css';
import { BrowserRouter as Router, Route, Routes} from "react-router-dom";
import AddAppointmentForm from "./views/AddAppointmentForm";
export const AppContext = createContext();
export const AppUpdateContext = createContext();
function App() {
const [appointments, setAppointments] = useState([])
const updateAppointments = (apptList) => {
setAppointments(apptList)
}
return (
<AppContext.Provider value={ appointments }>
<AppUpdateContext.Provider value={ updateAppointments }>
<Router>
<Routes>
<Route path="/" element={<Dashboard appointments={appointments} />} />
{/* <Route path="/add" element={<AddAppointmentForm />} /> TBA */}
</Routes>
</Router>
</AppUpdateContext.Provider>
</AppContext.Provider>
);
}
export default App;
Dashboard.js
import { useEffect, useContext} from "react";
import { AppContext } from "../App";
import { AppUpdateContext } from "../App";
import AppointmentList from "../components/AppointmentList";
import Header from "../components/Header";
// function Dashboard() { // this is how it used to be
function Dashboard(props) {
const appointments = useContext(AppContext)
const setAppointments = useContext(AppUpdateContext)
const fetchAppointmentList = async () => {
const res = await fetch("http://localhost:5000/appointments");
const data = await res.json();
return data;
}
useEffect(() => {
const getAppointments = async () => {
const appointmentsFromServer = await fetchAppointmentList();
setAppointments(appointmentsFromServer);
}
getAppointments();
console.log("ñññññ",appointments)
}, []);
console.log("aagh",appointments)
return (
<div style={dashboardStyle}>
<Header />
{/* {appointments.lenght>0 ? (<AppointmentList />) : <p>empty</p>} this is how it used to be */}
<AppointmentList appointments={props?.appointments}/>
</div>
);
}
const dashboardStyle = {
maxWidth: "31.25rem",
overflow: "auto",
minHeight: "18.75rem",
border: "1px solid steelblue",
margin: "1.875rem auto",
padding: ".5rem",
boxSizing: "border-box",
}
export default Dashboard;
AppointmentList.js
import Appointment from "./Appointment";
import { AppContext } from "../App";
import { useContext } from "react";
function AppointmentList({ appointments }) {
// function AppointmentList() { // this is how it used to be
// const { appointments, setAppointments } = useContext(AppContext)
console.log("appList",appointments) // this is how it used to be
return (
<>
{
appointments.map(appt => (
<Appointment key={appt.id} appointment={appt} />
))
}
</>
);
}
export default AppointmentList;
Why does optional chaining allows rendering when fetching data through
useEffect in an app that uses context?
<AppointmentList appointments={props?.appointments}/>
It allows rendering by preventing accidental accesses into potentially null or undefined objects. The only way props could be undefined though is if you just simply don't declare it, i.e. const Dashboard = () => {.... vs const Dashboard = (props) => {.....
You are drilling the appointments state through props. AppointmentList can use the AppContext context to access the appointments state, while Dashboard can use the AppUpdateContext context to update the appointments state.
App
function App() {
const [appointments, setAppointments] = useState([]);
const updateAppointments = (apptList) => {
setAppointments(apptList);
};
return (
<AppContext.Provider value={{ appointments }}> // <-- need object here
<AppUpdateContext.Provider value={{ updateAppointments }}> // <-- and here
<Router>
<Routes>
<Route path="/" element={<Dashboard />} /> // <-- don't pass props
</Routes>
</Router>
</AppUpdateContext.Provider>
</AppContext.Provider>
);
}
Dashboard
function Dashboard() { // <-- no props
const { updateAppointments } = useContext(AppUpdateContext); // <-- access from context
const fetchAppointmentList = async () => {
const res = await fetch("http://localhost:5000/appointments");
const data = await res.json();
return data;
};
useEffect(() => {
const getAppointments = async () => {
const appointmentsFromServer = await fetchAppointmentList();
updateAppointments(appointmentsFromServer);
}
getAppointments();
}, []);
return (
<div style={dashboardStyle}>
<Header />
<AppointmentList /> // <-- don't pass props
</div>
);
}
AppointmentList
function AppointmentList() { // <-- no props
const { appointments } = useContext(AppContext); // <-- access from context
return appointments.map(appt => (
<Appointment key={appt.id} appointment={appt} />
));
}
I am trying to pass a function on the sectionOne, then call sectionOne from another component but I am getting an error.
Error: Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
import React from 'react';
import {Home} from './home';
export const appTabBar = {
sectionOne: function sectionOne() {
return (
<>
<Home />
</>
);
},
};
import React from 'react';
import PropTypes from 'prop-types';
export const DrawerSection = props => {
const {sectionOne} = props;
return (
<>
<div>{sectionOne}</div>
</>
);
};
DrawerSection.propTypes = {
sectionOne: PropTypes.any,
};
You want to render sectionOne as a React component, and user-defined components must be capitalized. You can either rename sectionOne in the file where it is declared or assign it to a variable in the file where it is used:
export const DrawerSection = props => {
const {sectionOne: SectionOne} = props;
return (
<>
<div><SectionOne /></div>
</>
);
};
I would like to know how can I avoid unnecessary updates on a child component that does not receive props, but uses useContext.
I've added React.memo to the Child2 component for me to make some demo tests.
As you can see, when I change the input value which uses context to pass the data to child1, Child2 does not re-render, as React.memo prevents this behavior.
How can I prevent unnecessary renders in Child1 as well? I know that React.memo won't work as it needs props, and with context this will not happen, as no props are being passed down to this component
App.js
import React, {useState} from 'react';
import './App.css';
import Child1 from "./Child1";
import Child2 from "./Child2";
import SillyContext from './context'
function App() {
const [name, setName] = useState('Radha');
const [count, setCount] = useState(0)
const increaseCount = () => {
setCount(prevState => prevState + 1)
}
return (
<div className="App">
<Child2 count={count}/>
<button onClick={increaseCount}>Increase</button>
<input type="text" value={name} onChange={(event => setName(event.target.value))}/>
<SillyContext.Provider value={{name}}>
<Child1/>
</SillyContext.Provider>
</div>
);
}
export default App;
Child1
import React, {useContext} from "react";
import SillyContext from './context'
export default function Child1() {
const sillyContext = useContext(SillyContext)
console.log('[Child 1 ran]')
return (
<div>
<div>[Child 1]: {sillyContext.name}</div>
</div>
)
}
Child2
import React from 'react'
export default React.memo(function Child2(props) {
console.log('[Child2 Ran!]')
return <div>[Child2] - Count: {props.count}</div>
})
The major problem due to which Child1 re-renders when count is updated is because you are passing a new object reference to Context Provider everytime.
Also If the App component re-renders, all element rendered within it re-render, unless they implement memoization or are PureComponent or use shouldComponentUpdate
You can make 2 changes to fix your re-rendering
wrap Child1 with React.memo
Use useMemo to memoize the object passed as value to provider
App.js
function App() {
const [name, setName] = useState("Radha");
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount(prevState => prevState + 1);
};
const value = useMemo(() => ({ name }), [name]);
return (
<div className="App">
<Child2 count={count} />
<button onClick={increaseCount}>Increase</button>
<input
type="text"
value={name}
onChange={event => setName(event.target.value)}
/>
<SillyContext.Provider value={value}>
<Child1 />
</SillyContext.Provider>
</div>
);
}
Child2
const Child2 = React.memo(function(props) {
console.log("[Child2 Ran!]");
return <div>[Child2] - Count: {props.count}</div>;
});
Child 1
const Child1 = React.memo(function() {
const sillyContext = useContext(SillyContext);
console.log("[Child 1 ran]");
return (
<div>
<div>[Child 1]: {sillyContext.name}</div>
</div>
);
});
Working demo
You can use useMemo inside Child1 component. It doesn't solve rerendering problem, but it is some kind of improvement.
Following snippet is third variant proposed by React core dev
we could make our code a bit more verbose but keep it in a
single component by wrapping return value in useMemo and specifying
its dependencies. Our component would still re-execute, but React
wouldn't re-render the child tree if all useMemo inputs are the same.
const { useState, useEffect, createContext, useContext, useMemo } = React;
const SillyContext = createContext();
const Child2 = React.memo(({count}) => {
return <div>Count: {count}</div>
})
const Child1 = () => {
const {name} = useContext(SillyContext);
return useMemo(() => {
console.log('Child1');
return <div>
<div>Child 1: {name}</div>
</div>}, [name])
}
const App = () => {
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount(prevState => prevState + 1)
}
return <div>
<Child2 count={count}/>
<button onClick={increaseCount}>Increase</button>
<Child1/>
</div>
}
ReactDOM.render(
<SillyContext.Provider value={{name: 'test'}}>
<App />
</SillyContext.Provider>,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
I'm building a quick authentication higher order component in Next.js and am getting some problems with the following code:
import SignIn from "../components/sign-in";
import { connect } from "react-redux";
import { useRouter } from "next/router";
const AuthenticationCheck = WrappedComponent => {
const { isAuthenticated, ...rest } = props;
const router = useRouter();
const protectedPages = ["/colours", "/components"];
const pageProtected = protectedPages.includes(router.pathname);
return !isAuthenticated && pageProtected ? (
<SignIn />
) : (
<WrappedComponent {...rest} />
);
};
function mapStateToProps(state) {
return {
isAuthenticated: state.auth.isAuthenticated
};
}
export default connect(mapStateToProps)(AuthenticationCheck);
If I change the code to remove redux & connect, it looks like this, and works perfectly.
const AuthenticationCheck = WrappedComponent => {
const { ...rest } = props;
const router = useRouter();
const protectedPages = ["/colours", "/components"];
const pageProtected = protectedPages.includes(router.pathname);
return pageProtected ? <SignIn /> : <WrappedComponent {...rest} />;
};
export default AuthenticationCheck;
I've been reading every SO, redux documentation etc for the last couple of hours, and I can't really find anything that matches what I'm doing, although I can't believe it's an uncommon use case.
Am I missing something obvious?
Solution: (Thankyou Dima for your help!)
So the final code that ended up working is:
import SignIn from "../components/sign-in";
import { connect } from "react-redux";
import { useRouter } from "next/router";
import { compose } from "redux";
const AuthenticationCheck = WrappedComponent => {
const authenticationCheck = props => {
const { isAuthenticated, ...rest } = props;
const router = useRouter();
const protectedPages = ["/colours", "/components"];
const pageProtected = protectedPages.includes(router.pathname);
return !isAuthenticated && pageProtected ? (
<SignIn />
) : (
<WrappedComponent {...rest} />
);
};
return authenticationCheck;
};
function mapStateToProps(state) {
return {
isAuthenticated: state.auth.isAuthenticated
};
}
export default compose(connect(mapStateToProps), AuthenticationCheck);
This works perfectly! 🙂
connect expects to get React component as a last argument, but you are sending HOC instead. You need to put connect and wrapper inside compose function. See below
import React from 'react'
import {compose} from 'redux'
import {connect} from 'react-redux'
import {doSomething} from './actions'
const wrapComponent = Component => {
const WrappedComponent = props => {
return (
<Component {...props} />
)
}
return WrappedComponent
}
const mapStateToProps = state => {
return {
prop: state.prop,
}
}
export default compose(
connect(mapStateToProps, {doSomething}),
wrapComponent
)
And the useit like this.
import React from 'react'
import withWrapper from 'your/path'
const Component = props => 'Component'
export default withWrapper(Component)