How to force component render in React - reactjs

I am making a simple ajax call to an API that produces data every 5 seconds, the data is received correctly using observables, and the data state is updated as expected.
the problem is: the page is rendering only once when it is loaded, and not when date is changed using the setData function. if I reload the page by navigating to another link and then come back the data shows as expected
her is the code:
function MyComponent() {
const [data, setData] = useState(RxJSStore.getData())
useEffect(() => {
RxJSStore.addChangeListener(onChange);
if (data.length === 0) loadDataRxJSAction()
return () => RxJSStore.removeChangeListener(onChange)
}, [data.length])
function onChange() {
setData(RxJSStore.getData())
}
return (
<>
<List data={data} />
</>
)
}

There is a way around this by forcing the component to render every time the onChange method is called. It is written in the documentation that this must only be used for testing purposes, but as the situation does not have another solution this might be the solution for this use case.
import { useRefresh } from 'react-tidy'
function MyComponent() {
const [data, setData] = useState(RxJSStore.getData())
const refresh = useRefresh()
useEffect(() => {
RxJSStore.addChangeListener(onChange);
if (data.length === 0) loadDataRxJSAction()
return () => RxJSStore.removeChangeListener(onChange)
}, [data.length])
function onChange() {
setWarnings(RxJSStore.getWarnings())
refresh()
}
return (
<>
<List data={data} />
</>
)
}

Related

Why do my ReactJS changes disappear on refreshing the page

I'm new to React and I'm trying to render a list of Pokemons.
I'm fetching the pokemon names from a local file and then using those names to trigger HTTP calls to my backend server, to get the pokemon images. Here's my code so far:
function PokemonList(props) {
const [pokemonList, setPokemonList] = useState([]);
const [isFetched, setIsFetched] = useState(false);
const [renderedList, setRenderedList] = useState([]);
useEffect(() => {
fetch(raw)
.then((r) => r.text())
.then((text) => {
setPokemonList(text.split("\n"));
setIsFetched(true);
});
}, []);
// I believe this is to blame, but I don't know why. On refresh, pokemonList becomes empty
useEffect(() => {
setRenderedList(populateRenderedList(pokemonList));
}, []);
return !isFetched ? null : (
<div className="list">
{renderedList}
<PaginationBar listSize={renderedList.length} list={renderedList} />
</div>
);
}
function populateRenderedList(pokemonList) {
let pokemonAPI = "https://pokeapi.co/api/v2/pokemon-form/";
const temp = [];
console.log(pokemonList);
pokemonList.forEach((pokemonName) => {
let renderedPokemon = (
<a className="pokemonLink" href={pokemonAPI + pokemonName.toLowerCase()}>
<PokemonDiv name={pokemonName.toLowerCase()} />
<h3>{pokemonName}</h3>
</a>
);
temp.push(renderedPokemon);
});
return temp;
}
As I have commented on the code, the 'pokemonList' renders fine when I make any changes to the PokemonList function. But the moment I refresh my page, 'pokemonList' becomes empty. Why is that?
I previously was not using 'useState' to populate my 'renderedList' list. So I believe the problem is happening because I'm using 'useState' , but I don't know why that's happening.
I had tried making 'renderedList' not a state, but I had to, for I am thinking about passing it as props to another child component, in order to change it's state.

Call child component based on state value in parent component using React

I am trying to pass data to child component(grid) from parent component. In parent component, I have created a state for the data which gets updated once the data fetch from an api is finished. Currently, I check if the state length is greater than 0 and only then I call child component.
const ParentComponent()=>{
const [gridData, setGridData] = useState([]);
useEffect(() => { getDataFunction (); }
const getDataFunction = async () => {
try { //apis to get data }
setGridData(apiData);
}
return (
{gridData.length > 0 && <ChildComponent Grid tableData={gridData}}
);
}
The above code works fine. It displays the data when there is some value in gridData. But I would like to display an empty table while the api is being called. Or if the api has no data, even then an empty table needs to be displayed.
Child component:
const Grid = React.forwardRef(({ tableData, columnData, ...props}, ref) => {
let activeColumn = [...columnData];
const onGridReady = params => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
setGridRowData(tableData);
params.api.applyTransaction({ add: tableData });
}
return (
<AgGridReact
onGridReady={onGridReady}
columnDefs={activeColumn}
pagination={true}
</AgGridReact>
);
});
If I just use gridData instead of gridData.length, then even after the value of the state is updated, the table is not updated. It will always be empty even if later the data is fetched from an api. How do I make child component update when gridData state changes ?
return (
<>
{gridData.length > 0 && <ChildComponent Grid tableData={gridData} />}
{gridData.length === 0 && <div>Hi there</div>}
</>
);
You simply render it conditionally based on length
I hope you have written useEffect code properly, bcoz the syntax seems incorrect.
useEffect(() => {
const getDataFunction = async () => {
try {
setGridData(apiData);
}
}
getDataFunction()
},[])
return (
<ChildComponent Grid tableData={gridData}
);
Use gridData.length > 0 line while rendering your child component
and for Empty Table while loading you can use Skeleton on loading state.

Losing state between renders if component is defined in another component

codesandbox here: https://codesandbox.io/s/restless-haze-v01wv?file=/src/App.js
I have a Users component which (when simplified) looks something like this:
const Users = () => {
const [toastOpen, setToastOpen] = useState(false)
// functions to handle toast closing
return (
<EditUser />
<Toast />
)
}
const EditUser = () => {
[user, setUser] = useState(null)
useEffect(() => {
const fetchedUser = await fetchUser()
setUser(fetchedUser)
}, [])
// this approach results in UserForm's username resetting when the toast closes
const Content = () => {
if (user) return <UserForm user={user} />
else return <div>Loading...</div>
}
return <Content />
// if I do this instead, everything's fine
return (
<div>
{
user ? <UserForm user={user} /> : <div>Loading...</div>
}
</div>
)
}
const UserForm = ({ user }) => {
const [username, setUsername] = useState(user.name)
return <input value={username}, onChange={e => setUsername(e.target.value)} />
}
While viewing the UserForm page while a Toast is still open, the UserForm state is reset when the Toast closes.
I've figured out that the issue is the Content component defined inside of EditUser, but I'm not quite clear on why this is an issue. I'd love a walkthrough of what's happening under React's hood here, and what happens in a "happy path"
You have defined Content inside EditUser component which we never do with React Components, because in this situtaion, Content will be re-created every time the EditUser is re-rendered. (surely, EditUser is going to be re-rendered few/many times).
So, a re-created Content component means the old Content will be destroyed (unmounted) and the new Content will be mounted.
That's why it is be being mounted many times and hence resetting the state values to initial values.
So, the solution is to just define it (Content) outside - not inside any other react component.
The culprit was EditUser's Content function, which predictably returns a brand new instance of each time it's called.

Unable to display promise data inside a method in render

I realize I can't return objects in a react render but I'm having a problem figuring out how to do this.
Error message:
Invariant violation, objects are not valid as a React child error
My method:
displayListItemComponent = item => {
return this.getConcernsDB(item.values)
.then(returnedConcerns => {
if(returnedConcerns.length) {
returnedConcerns.forEach(el => {
return(el.name);
})
}
}
)
}
Partial render:
<CollapseBody>
{ el.details.map(item => (
this.displayListItemComponent(item)
))}
</CollapseBody>
Here I am trying to return el.name from displayListItemComponent
I am attempting to return displayListItemComponent in my react render.
My assumption was that by returning the function this.getConcernsDB that it would chain down and it would return el.name. How can I achieve this?
Your method returns a Promise. Render should be synchronous and have no side effects (e.g. it should only return allowed values from props and state). Therefore you need to store your data in state, and render elements from state
You could do something like this. Also what does el.name contain?
displayListItemComponent =(props) => {
const {item} = props;
const [data, setData] = useState();
useEffect(() => {
// i see you did this.getConcernsDB. where is it coming from?
getConcernsDB(item.values)
.then(returnedConcerns => {
setData(returnedConcerns.values(el => el.name))
)
}, [])
if(!data) return <SomeLoadingSpinner />
// now you have access to the names in `data` variable. Return whatever component you want using the `data`
}

Why am I able to conditionally call a hook one way, but not the other?

Context:
When I refresh the dashboard, useHasPermission makes an async call to determine if the
user has access to somePermission.
Issue:
hasPermission initially evaluates to false, but once the async call has completed hasPermission evaluates to true.
This causes the useQuery, Apollo hook to not be called on the first render, and then called on the second render.
The following error is shown:
"Rendered more hooks than during the previous render."
Question:
Why does this error only happen in example A and not example B?
// Example A: Does not work
const Dashboard = () => {
const hasPermission = useHasPermission([somePermission]);
const getDashboardData = () => {
const { loading, data, error } = useQuery(SOME_QUERY, {
variables: { ...someVars }
});
return <Table ={data} loading={loading} error={error}><Table>
};
return (
{hasPermission ? getDashboardData() : null}
<Announcements></Announcements>
)
}
// Example B: Works
const Dashboard = () => {
const hasPermission = useHasPermission([somePermission]);
const DashboardData = () => {
const { loading, data, error } = useQuery(ACCOUNTS_FOR_CUSTOMER_DASHBOARD, {
variables: { ...someVars }
});
return <Table ={data} loading={loading} error={error}><Table>
};
return (
{hasPermission ? (
<DashboardData></DashboardData>
) : null}
<Announcements></Announcements>
)
}
Hooks aren't meant to be conditionally used.
In the first example, you are conditionally calling a function that uses a new hook and returns JSX, so this breaks the rules of hooks.
In the second example, you are creating a new component DashboardData that mounts conditionally. So because it is a new component it is allowed.
So the difference between the two is in "A" useQuery belongs to the Dashboard component, where in "B" it belongs to DashboardData.

Resources